Skip to content

refactor(di): shared AddSharpClientViewModels registration to prevent host drift#24

Merged
HarryCordewener merged 1 commit into
masterfrom
worktree-agent-a130d5c8e72911a11
Jul 1, 2026
Merged

refactor(di): shared AddSharpClientViewModels registration to prevent host drift#24
HarryCordewener merged 1 commit into
masterfrom
worktree-agent-a130d5c8e72911a11

Conversation

@HarryCordewener

Copy link
Copy Markdown
Member

Problem: host-DI drift

The two app hosts each registered the six SharpClient.Core.Presentation view models independently:

  • MAUI host — src/SharpClient.App/MauiProgram.cs
  • Web host — src/SharpClient.Web/Program.cs

Because the two lists were maintained separately, they drifted. TriggerAliasEditorViewModel was registered on Web but never on MAUI, so the Rules page crashed on Android with No registered service of type 'TriggerAliasEditorViewModel'.

Fix

Add a single shared extension AddSharpClientViewModels(this IServiceCollection, ServiceLifetime perViewLifetime) in SharpClient.UI (a Razor class library already referenced by both hosts) — src/SharpClient.UI/ServiceCollectionExtensions.cs.

  • The three shared VMs (SessionsViewModel, ProtocolPanelViewModel, SettingsViewModel) are always registered as Singleton.
  • The three per-view VMs (WorldManagerViewModel, HistorySearchViewModel, TriggerAliasEditorViewModel) use the caller-supplied perViewLifetime via factory-based ServiceDescriptors, preserving the constructor-injection lambdas.

Both hosts now call the shared method — MAUI with ServiceLifetime.Transient, Web with ServiceLifetime.Scoped — so each host keeps its exact previous lifetimes and the two can no longer diverge. Added the Microsoft.Extensions.DependencyInjection.Abstractions package reference to SharpClient.UI.

Supersedes #22

PR #22 hot-fixed the single missing TriggerAliasEditorViewModel registration in MauiProgram. This refactor moves all VM registrations into the shared method (which registers TriggerAliasEditorViewModel for MAUI too), so it fixes the same crash structurally. #22 can be closed.

Verified

  • dotnet build src/SharpClient.Web/SharpClient.Web.csproj — clean (0 warnings, 0 errors).
  • dotnet build src/SharpClient.App/SharpClient.App.csproj -f net10.0-android — clean (0 warnings, 0 errors).
  • dotnet run --project tests/SharpClient.Tests — 198 passed, failed: 0.
  • dotnet run --project tests/SharpClient.UI.Tests — 47 passed, failed: 0.
  • dotnet run --project tests/SharpClient.Data.Tests — 17 passed, failed: 0.
  • The set of VMs the shared method registers equals the set the UI injects (grep -rhoE "@inject [A-Za-z]+ViewModel" src/SharpClient.UI → Sessions, ProtocolPanel, Settings, WorldManager, HistorySearch, TriggerAliasEditor — all six).

🤖 Generated with Claude Code

… host drift

The Web host (Web/Program.cs) and the MAUI host (App/MauiProgram.cs) each
registered the six presentation view models independently, and they drifted:
TriggerAliasEditorViewModel was registered on Web but not MAUI, which crashed
the Rules page on Android with "No registered service of type
'TriggerAliasEditorViewModel'".

Extract a single AddSharpClientViewModels(ServiceLifetime perViewLifetime)
extension in SharpClient.UI (referenced by both hosts). It registers the three
shared VMs (Sessions, ProtocolPanel, Settings) as Singleton and the three
per-view VMs (WorldManager, HistorySearch, TriggerAliasEditor) with the
caller-supplied lifetime. MAUI passes Transient, Web passes Scoped, preserving
each host's previous lifetimes exactly. Both hosts now call the shared method,
so the two can no longer diverge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
Copilot AI review requested due to automatic review settings July 1, 2026 15:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR eliminates DI registration drift between the MAUI and Web hosts by moving the shared SharpClient.Core.Presentation view model registrations into a single SharpClient.UI IServiceCollection extension method, ensuring both hosts stay aligned while preserving their existing lifetimes.

Changes:

  • Added AddSharpClientViewModels(IServiceCollection, ServiceLifetime) in SharpClient.UI to centrally register the six view models.
  • Updated both hosts to use the shared registration method (MAUI: Transient per-view; Web: Scoped per-view) while keeping shared VMs as Singleton.
  • Added an explicit Microsoft.Extensions.DependencyInjection.Abstractions package reference to SharpClient.UI.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/SharpClient.Web/Program.cs Replaces per-VM registrations with a single shared AddSharpClientViewModels(ServiceLifetime.Scoped) call.
src/SharpClient.App/MauiProgram.cs Replaces per-VM registrations with AddSharpClientViewModels(ServiceLifetime.Transient) to match MAUI lifetimes.
src/SharpClient.UI/ServiceCollectionExtensions.cs Introduces the shared view model DI registration extension method used by both hosts.
src/SharpClient.UI/SharpClient.UI.csproj Adds DI abstractions package reference needed for ServiceDescriptor usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

sp.GetRequiredService<ISessionHistory>(),
sp.GetRequiredService<IWorldStore>()));
// Registered via the shared extension so MAUI and Web stay in lockstep (no host drift).
// Per-view view models are Scoped here to match the Web's per-request scope.
@HarryCordewener
HarryCordewener merged commit 3192c02 into master Jul 1, 2026
3 checks passed
@HarryCordewener
HarryCordewener deleted the worktree-agent-a130d5c8e72911a11 branch July 1, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants